/*
 * Copyright 2003-2005 (c) Live Media Pty Ltd. <argot@einet.com.au> 
 *
 * This software is licensed under the Argot Public License 
 * which may be found in the file LICENSE distributed 
 * with this software.
 *
 * More information about this license can be found at
 * http://www.einet.com.au/License
 * 
 * The Developer of this software is Live Media Pty Ltd,
 * PO Box 4591, Melbourne 3001, Australia.  The license is subject 
 * to the law of Victoria, Australia, and subject to exclusive 
 * jurisdiction of the Victorian courts.
 */

#include "../Types.h"

#include "chunkbuffer.h"

int CHUNKSIZE = BUFSIZE;    

static chunk_data * chunk_data_free_chunks = NULL;
static int chunk_data_count = 0;


static chunk_data * chunk_buffer_find_chunk( chunk_buffer * buffer, int offset );

int chunkBufferReader( type_input_stream * in, void * data, char * buffer, int size )
{
	chunk_buffer * brd = (chunk_buffer *) data;
	
	return chunk_buffer_read_buffer( brd, buffer, size );
}

int chunkBufferWriter( type_output_stream * out, void * data, char * buffer, int size )
{
	chunk_buffer * cb = (chunk_buffer *) data;
	
	return chunk_buffer_write_buffer( cb, buffer, size );
}

int chunk_buffer_write_stream( chunk_buffer * buffer, int offset, int length, type_output_stream * out )
{
	if ( offset >= buffer->length ) return -1;
	if ( offset < 0 ) return -1;
	
	// crop the length to read if we would go over the end.
	if ( buffer->offset+length >= buffer->length ) 
		length = buffer->length - offset;
	
	int toWrite = length;
	int written = 0;
	
	int chunkOffset = offset % CHUNKSIZE;
	chunk_data * currentChunk = chunk_buffer_find_chunk( buffer, offset );

	while( toWrite > 0 )
	{
      	int w;
      	
      	// Will the data to write go past the end of the chunk?
	  	if ( toWrite >= (CHUNKSIZE - chunkOffset))
  		{
  			
  			w = (CHUNKSIZE - chunkOffset);

 	  		//currentChunk.put( buffer, offset+written, w );
  			//memcpy( currentChunk->buffer+chunkOffset, data+written, w );
  			type_output_write_buffer( out, currentChunk->buffer+chunkOffset, w );
  			
  			written += w;
			toWrite -= w;
  		
  			currentChunk = currentChunk->next;
  			chunkOffset = 0;
  		}
  		else
  		{
			w = toWrite;
			
 			//currentChunk.put( buffer, offset+written, w );
  			//memcpy( currentChunk->buffer+chunkOffset, data+written, w );
  			type_output_write_buffer( out, currentChunk->buffer+chunkOffset, w );
  					
	  		written += w;
			toWrite -= w;
  		}
  	}
  	return written;	
}




chunk_data * chunk_data_get()
{
	if ( chunk_data_free_chunks == NULL )
	{
		chunk_data_count++;
		chunk_data * buffer = type_malloc( sizeof(chunk_data) );
		buffer->next = NULL;
		return buffer;
	}
	else
	{
		chunk_data * buffer = chunk_data_free_chunks;
		chunk_data_free_chunks = chunk_data_free_chunks->next;
		buffer->next = NULL;
		return buffer;
	}
}

void chunk_data_release( chunk_data * chunk )
{
	if ( chunk_data_free_chunks == NULL )
	{
		chunk_data_free_chunks = chunk;
	}
	else
	{
		chunk->next = chunk_data_free_chunks;
		chunk_data_free_chunks = chunk;
	}
}

int chunk_data_allocated()
{
	return chunk_data_count;
}


/**
 *  ChunkByteBuffer
 */
void chunk_buffer_init( chunk_buffer * buffer )
{
	buffer->length = 0;
	buffer->offset = 0;
	buffer->closed = false;
	buffer->gotOutputStream = false;
	buffer->dataChunks = chunk_data_get();		
	buffer->lastChunk = buffer->dataChunks;
	buffer->lastUsedChunk = buffer->dataChunks;
	buffer->lastChunkIndex = 0;
}

void chunk_buffer_clean( chunk_buffer * buffer )
{
	while( buffer->dataChunks != NULL )
	{
		chunk_data * chunk = buffer->dataChunks;
		buffer->dataChunks = buffer->dataChunks->next;
		chunk->next = NULL;
		chunk_data_release( chunk );
	}
}



void chunk_buffer_add_chunk( chunk_buffer * buffer, chunk_data * newChunk )
{
	buffer->lastChunk->next = newChunk;
	buffer->lastChunk = newChunk;
}


/**
 * Find a chunk based on offset. We expect to be moving
 * forward most of the time, so algorithm is designed for
 * that.
 */
chunk_data * chunk_buffer_find_chunk( chunk_buffer * buffer, int offset )
{
	int newChunkIndex = (offset/CHUNKSIZE);
	
	if ( buffer->lastChunkIndex == newChunkIndex )
	{
		return buffer->lastUsedChunk;
	}
	else if ( buffer->lastChunkIndex < newChunkIndex )
	{
		int toMove = newChunkIndex - buffer->lastChunkIndex;
		while ( toMove > 0 )
		{
			buffer->lastUsedChunk = buffer->lastUsedChunk->next;
			toMove--;
		}
		buffer->lastChunkIndex = newChunkIndex;
		return buffer->lastUsedChunk;
	}
	else
	{
		buffer->lastUsedChunk = buffer->dataChunks;
		buffer->lastChunkIndex = newChunkIndex;
		
		int toMove = newChunkIndex;
		while( toMove > 0 )
		{
			buffer->lastUsedChunk = buffer->lastUsedChunk->next;
			toMove--;
		}
		return buffer->lastUsedChunk;
	}
} 

/**
 * Write a byte buffer to the chunkByteBuffer.
 */

int chunk_buffer_write_buffer( chunk_buffer * buffer, char * data, int length )
{
	
	if ( buffer->closed == true ) 
		return -1;

	int chunkOffset = buffer->length % CHUNKSIZE;
	chunk_data * currentChunk = chunk_buffer_find_chunk( buffer, buffer->length );

	int toWrite = length;
	int written = 0;
 
	//currentChunk.position( chunkOffset );

	while( toWrite > 0 )
	{
      	int w;
      	
      	// Will the data to write go past the end of the chunk?
	  	if ( toWrite >= (CHUNKSIZE - chunkOffset))
  		{
  			
  			w = (CHUNKSIZE - chunkOffset);

 	  		//currentChunk.put( buffer, offset+written, w );
  			memcpy( currentChunk->buffer+chunkOffset, data+written, w );
  			
  			written += w;
	  		buffer->length += w;
			toWrite -= w;
  		
  			currentChunk = chunk_data_get();
  			//_dataChunks.add( currentChunk );
  			chunk_buffer_add_chunk( buffer, currentChunk );
  			chunkOffset = 0;
  		}
  		else
  		{
			w = toWrite;
			
 			//currentChunk.put( buffer, offset+written, w );
  			memcpy( currentChunk->buffer+chunkOffset, data+written, w );
  		
	  		written += w;
  			buffer->length += w;
			toWrite -= w;
  		}
  	}
  	return written;
}

int chunk_buffer_write( chunk_buffer * buffer, int ch )
{
	if ( buffer->closed == true ) 
		return -1;

	int chunkOffset = buffer->length % CHUNKSIZE;
	chunk_data * currentChunk = chunk_buffer_find_chunk( buffer, buffer->length );

	//currentChunk.put( chunkOffset, (byte) ch );
	currentChunk->buffer[chunkOffset] = ch;

	buffer->length++;
	chunkOffset++;

	if ( chunkOffset >= CHUNKSIZE )
	{
		currentChunk = chunk_data_get();
		//_dataChunks.add( currentChunk );
		chunk_buffer_add_chunk( buffer, currentChunk );
	}
	return 0;
}
 
int chunk_buffer_read( chunk_buffer * buffer )
{
	if ( buffer->offset >= buffer->length ) return -1;  //EOF
	if ( buffer->offset < 0 ) return -1;        //EOF

	int chunkOffset = buffer->offset % CHUNKSIZE;
	chunk_data * currentChunk = chunk_buffer_find_chunk( buffer, buffer->offset );
	
	int value = currentChunk->buffer[ chunkOffset ];
	//int value = currentChunk[ chunkOffset ];
	
	//if ( value < 0 ) value = (value & 0x7F) + 128;
	// if ( value > 128 ) System.out.println( "Value: " + value );
	//cout << "offset " << offset << " chunkOffset " << chunkOffset << " read " << value << endl;
	return value;
}


int chunk_buffer_read_buffer( chunk_buffer * buffer, char * data, int length )
{
	if ( buffer->offset >= buffer->length ) return -1;
	if ( buffer->offset < 0 ) 
		return -1;
	
	// crop the length to read if we would go over the end.
	if ( buffer->offset+length >= buffer->length ) 
		length = buffer->length - buffer->offset;
	
	int toRead = length;
	int read = 0;
	
	while ( toRead > 0 )
	{
		int chunkOffset = (buffer->offset+read) % CHUNKSIZE;
		chunk_data * currentChunk = chunk_buffer_find_chunk( buffer, buffer->offset+read );
		int r = 0;

		// Will our read take us past the end of the buffer
		if ( toRead > (CHUNKSIZE - chunkOffset) )
		{
			// read to the end of the buffer
			r = (CHUNKSIZE - chunkOffset);

			//currentChunk.position( chunkOffset );
			//currentChunk.get( buffer, pos+read, r );
			memcpy( data+read, currentChunk->buffer+chunkOffset, r );
			
			read += r;
			toRead -= r;

		}
		// We're reading within the bufer.
		else
		{
			// read the last bytes.
			r = toRead;

			//currentChunk.position( chunkOffset );
			//currentChunk.get( buffer, pos+read, r );
			memcpy( data+read, currentChunk->buffer+chunkOffset, r );
			
			read+= r;
			toRead -= r;
		}
		
	}
	
	buffer->offset = buffer->offset + read;
	return read;
}



void chunk_buffer_close( chunk_buffer * buffer )
{
	buffer->closed = true;
}


int chunk_buffer_length( chunk_buffer * buffer )
{
	return buffer->length;
}

